package main

import (
	"strings"
	"fmt"
	"flag"
	"os"
	"path/filepath"
)

const (
	EXIT_SUCCESS int = iota
	EXIT_ENV_INVALID
	EXIT_CONFIG_INVALID
	EXIT_SPECIFIED_PATH_INVALID
	EXIT_HOME_INVALID
	EXIT_SSH_ERROR
	EXIT_SCP_ERROR
)

func main() {
	// query program name by call
	program := os.Args[0][strings.LastIndex(os.Args[0], "/") + 1 : len(os.Args[0])]

	// query environment
	user, user_ok := os.LookupEnv("USER");
	home, home_ok := os.LookupEnv("HOME");
	if !(user_ok && home_ok) {
		fmt.Fprintf(os.Stderr, "%s: error: environment variables $USER and $HOME need to be set\n", program)
		os.Exit(EXIT_ENV_INVALID)
	}

	// register help functions
	flag.Usage = func() {
		fmt.Fprintf(os.Stdout, "usage: %s [-s|--save] [-c|--config path] [-l|--list-config] [-e|--print-example-config] [-g|--generate-config path] [-G|--generate-config-auto] command [files]\n", program)
		// TODO: default generated by flag pkg is ugly, redo
		flag.PrintDefaults()
		os.Exit(EXIT_SUCCESS)
	}

	// add save flag
	var save bool
	default_save := false
	help_save := "saves the result of an operation if true (only for some operations)"
	flag.BoolVar(&save, "s", default_save, help_save)
	flag.BoolVar(&save, "save", default_save, help_save)

	// add configuration file path option (with alias for --config)
	var configuration_path string
	default_path := fmt.Sprintf("%s/.config/%s/%s", home, user, user)
	help_configuration_path := "specifies the file path of the configuration to be used"
	flag.StringVar(&configuration_path, "c", default_path, help_configuration_path)
	flag.StringVar(&configuration_path, "config", default_path, help_configuration_path)

	// add print example configuration flag
	var list_config bool
	default_list_config := false
	help_list_config := "lists current configuration obfuscating the secret and exits"
	flag.BoolVar(&list_config, "l", default_list_config, help_list_config)
	flag.BoolVar(&list_config, "list-config", default_list_config, help_list_config)

	// add print example configuration flag
	var print_example_config bool
	default_print_example_config := false
	help_print_example_config := "prints example configuration if true and exits"
	flag.BoolVar(&print_example_config, "e", default_print_example_config, help_print_example_config)
	flag.BoolVar(&print_example_config, "print-example-config", default_print_example_config, help_print_example_config)

	// add generate example configuration option
	var generate_config_path string
	default_generate_config_path := ""
	help_generate_config := "generates and example configuration to a specified path and exits"
	flag.StringVar(&generate_config_path, "g", default_generate_config_path, help_generate_config)
	flag.StringVar(&generate_config_path, "generate-config", default_generate_config_path, help_generate_config)

	// add generate example configuration auto flag
	var generate_config_auto bool
	default_generate_config_auto := false
	help_generate_config_auto := fmt.Sprintf("generates and example configuration to %s and exits", GetConfigDefaultPath())
	flag.BoolVar(&generate_config_auto, "G", default_generate_config_auto, help_generate_config_auto)
	flag.BoolVar(&generate_config_auto, "generate-config-auto", default_generate_config_auto, help_generate_config_auto)

	// finally parse everything and use arguments afterwards
	flag.CommandLine.Parse(os.Args[1:])

	// if user wants to print example configuration
	if print_example_config {
		fmt.Println(CONFIG_EXAMPLE)
		return
	}

	// if user wants to generate example configuration
	if generate_config_auto || generate_config_path != "" {
		// query default location and split to dir and file
		default_path := GetConfigDefaultPath()

		// in case user specified his own path
		if generate_config_path != "" {
			default_path = generate_config_path
			// take care to expand unexpanded tilde
			fmt.Printf(default_path, strings.HasPrefix(default_path, "~"))
			if strings.HasPrefix(default_path, "~") {
				default_path = strings.Replace(default_path, "~", GetHomeDir(), 1)
			}
		}
		dir, filename := filepath.Split(default_path)
		fmt.Printf("generating configuration at %s\n", default_path)
		os.MkdirAll(dir, os.ModePerm)

		// TODO: ask user in case file already exists?

		err := os.WriteFile(default_path, []byte(CONFIG_EXAMPLE), 0644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "fatal error: file %s could not be opened for reading\n", filename)
		}
		return
	}

	// get and check configuration
	config := GetConfiguration(&program, &configuration_path)
	if !IsConfigurationValid(&config) {
		fmt.Fprintf(os.Stderr, "%s: configuration is invalid: %s\n", program, ConfigurationToString(&config, true))
		os.Exit(EXIT_CONFIG_INVALID)
	}

	// if user wants to list current configuration
	if list_config {
		fmt.Println(ConfigurationToString(&config, true))
		return
	}

	// print available commands in case user needs them
	ExtendedUsage := func() {
		fmt.Printf("available commands are: `list`, `push [files]`, `pull [files]`, `commit`\n\n")
		flag.Usage()
	}

	// one command is necessary
	if flag.NArg() < 1 {
		ExtendedUsage()
	}

	// split command and (sometimes) optional positional file arguments
	arguments := flag.Args()
	command, files := arguments[0], arguments[1:]

	// show usage in case an invalid command is issued
	if IsImplemented(command) {
		Exec(command, &config, files, save)
	} else {
		ExtendedUsage()
	}
}